package com.xxwu.messagequeue.mqclient;

import com.xxwu.messagequeue.common.BinaryTool;
import com.xxwu.messagequeue.common.MqException;
import com.xxwu.messagequeue.common.Request;
import com.xxwu.messagequeue.common.Responds;
import com.xxwu.messagequeue.common.argumentsType.BasicPublishArguments;
import com.xxwu.messagequeue.common.argumentsType.BasicReturns;
import com.xxwu.messagequeue.common.argumentsType.SubScribeReturns;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class Connection {
    // 用于建立网络连接
    private Socket socket = null;

    // 该连接写对应的逻辑连接，channel
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();

    // 用于请求和响应的传递
    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    // 消费消息
    private ExecutorService messagePool = Executors.newCachedThreadPool();

    // 建立连接
    public Connection(String host, int port) throws IOException {
        socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        // 创建扫描线程用于读取服务器的响应
        Thread t = new Thread(()->{
            try{
                while(!socket.isClosed()){
                    Responds responds = readResponds();
                    log.info("ClientResponds.type={},responds.payload={}", responds.getType(),responds.getPayload());
                    // 处理响应
                    dispatch(responds);
                }
            } catch (SocketException e){
                log.info(e.getMessage());
            } catch (IOException | MqException | ClassNotFoundException e) {
                log.error("读取服务器响应发生错误！");
                e.printStackTrace();
            }
        });
        t.start();
    }

    // 关闭连接
    public void close() throws IOException {
        socket.close();
        channelMap.clear();
        inputStream.close();
        outputStream.close();
        messagePool.shutdownNow();
    }

    private void dispatch(Responds responds) throws IOException, ClassNotFoundException, MqException {
        // 服务器发送消息时独有的响应
        if(responds.getType() == 0xc){
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.frombytes(responds.getPayload());
            // 获取到当前消息对应的 channel
            Channel channel = channelMap.get(subScribeReturns.getConsumerTag());
            if(channel == null){
                throw new MqException("当前要处理消息的消费者不存在");
            }
            // 对消息进行处理,但这里使用扫描线程处理消息消费逻辑不合适，如果消费消息耗时很长
            // 那么读取响应就会阻塞，这里创建一个线程池对消息进行处理
            messagePool.submit(()->{
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag()
                            , subScribeReturns.getProperties(), subScribeReturns.getBody());
                } catch (IOException | MqException e) {
                    throw new RuntimeException(e);
                }
            });
        } else {
            // 其他请求对应的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.frombytes(responds.getPayload());
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if(channel == null){
                throw new MqException("当前要处理响应的消费者不存在");
            }
            // 存储进响应池中
            channel.putReturns(basicReturns);
        }
    }

    // 发送请求
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.payload);
        dataOutputStream.flush();
    }
    // 读取响应
    public Responds readResponds() throws IOException, MqException {
        Responds responds = new Responds();
        responds.setType(dataInputStream.readInt());
        responds.setLength(dataInputStream.readInt());
        byte[] payload = new byte[responds.getLength()];
        int actual = dataInputStream.read(payload);
        if(actual != responds.getLength()) {
            throw new MqException("响应格式不正确！");
        }
        responds.setPayload(payload);
        return responds;
    }

    // 创建 channel 对象
    public Channel createChannel() throws IOException {
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId, this);
        // 当前创建好了 channel 对象，还要通知服务器，在服务器相关业务代码处，也要将 channel 记录下来
        channelMap.put(channelId, channel);
        channel.createChannel();
        return channel;
    }
}
